﻿/**
 *  Distributed as part of Free.iso8583
 *  
 *  Free.iso8583 is ISO 8583 Message Processor library that makes message parsing/compiling esier.
 *  It will convert ISO 8583 message to a model object and vice versa. So, the other parts of
 *  application will only do the rest effort to make business process done.
 *  
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License or (at your option) any later version. 
 *  See http://gnu.org/licenses/lgpl.html
 *
 *  Developed by AT Mulyana (atmulyana@yahoo.com) 2009-2010, 2012, 2013
 *  The latest update can be found at sourceforge.net
 **/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Free.iso8583.config;

namespace Free.iso8583
{
    public abstract class IMessageHeader
    {
        public abstract int Length { get; internal set; }
        internal abstract byte[] Bytes { get; set; }
        public byte[] RoBytes { get { return (byte[])Bytes.Clone(); } }
    }

    public class MessageBitMap : IMessageHeader
    {
        private byte[] _bitMap;

        #region IMessageHeader Members
        public override int Length
        {
            get
            {
                return _bitMap.Length;
            }
            internal set
            {
                _bitMap = new byte[value];
            }
        }

        internal override byte[] Bytes
        {
            get { return _bitMap; }
            set { _bitMap = value; }
        }
        #endregion

        internal void SetBitOn(IList<int> bitOnList)
        {
            for (int i = 0; i < _bitMap.Length; i++) _bitMap[i] = 0;
            int maxPos = 8 * _bitMap.Length;
            foreach (int bitPos in bitOnList)
            {
                if (bitPos < 1 || bitPos > maxPos) continue;
                int bitPos2 = bitPos - 1;
                int bytePos = bitPos2 / 8;
                int subBitPos = bitPos2 % 8;
                _bitMap[bytePos] = (byte)(_bitMap[bytePos] | (0x80 >> subBitPos));
            }
        }

        public bool IsBitOn(int bitPos)
        {
            return IsBitOn(bitPos, _bitMap);
        }

        public static bool IsBitOn(int bitPos, byte[] bitMap)
        {
            int bitsCount = bitMap.Length * 8;
            if (bitPos < 1 || bitPos > bitsCount) return false;
            int bitPos2 = bitPos - 1;
            int bytePos = bitPos2 / 8;
            int subBitPos = bitPos2 % 8;
            return (bitMap[bytePos] & (0x80 >> subBitPos)) != 0;
        }
    }

    public class MessageTypeHeader : IMessageHeader
    {
        private byte[] _bytes;

        public MessageTypeHeader(byte[] bytes)
        {
            _bytes = bytes;
        }

        public override int Length
        {
            get { return _bytes.Length; }
            internal set { }
        }

        internal override byte[] Bytes
        {
            get { return _bytes; }
            set { }
        }
    }

    public class MessageHeader : IMessageHeader
    {
        private byte[] _bytes;

        #region IMessageHeader Members
        public override int Length
        {
            get
            {
                return Bytes.Length;
            }
            internal set
            {
                if (_bytes == null) _bytes = new byte[value];
                else Array.Resize<byte>(ref _bytes, value);
            }
        }

        internal override byte[] Bytes
        {
            get
            {
                if (GetFieldBytesFunc != null) return (byte[])GetFieldBytesFunc.DynamicInvoke(null);
                if (_bytes == null) throw new MessageProcessorException("Null value cannot be assigned to message header.");
                return _bytes;
            }
            set
            {
                _bytes = value;
            }
        }
        #endregion

        public Delegate GetFieldBytesFunc { get; internal set; }
    }
    
    public class MessageField
    {
        protected byte[] _bytes = null;
        protected String _string = null;
        protected decimal? _decimal = null;
        private int _fracDigits = 0;
        private bool _isOdd = false;
        
        public int? Length { get; internal set; }
        public bool VarLength { get; internal set; }
        public int BytesLength { get { return _bytes == null ? 0 : _bytes.Length; } }
        internal bool IsOdd { get { return _isOdd; } set { _isOdd = value; } }

        public virtual int GetBytesLengthFromActualLength(int actualLength)
        {
            if (actualLength <= 0) return 0;
            return (actualLength - 1) / 8 + 1;
        } 
        
        public int FracDigits
        {
            get
            {
                return _fracDigits;
            }
            internal set
            {
                if (value < 0)
                    throw new MessageProcessorException("Fractional length cannot be negative value.");
                if (_decimal != null && _fracDigits != value)
                {
                    String str = _string.Replace(".", "");
                    if (value > str.Length)
                        throw new MessageProcessorException("Fractional length assigned is greater than actual number of digits.");
                    //_bytes is kept intact
                    _string = str.Insert(str.Length - value, ".");
                    _decimal = _decimal * (decimal)Math.Pow(10, _fracDigits - value);
                }
                _fracDigits = value;
            }
        }

        internal virtual void SetValue(byte[] value)
        {
            if (VarLength && Length == null) Length = value.Length * 8;
            if (Length == null)
                throw new MessageProcessorException("The Length property still null (not yet set).");
            if (value.Length*8 != Length)
                throw new MessageProcessorException("The length of the assigned value is incompatible with Length property.");
            
            _bytes = value;
            _string = null;
            _decimal = null;
        }
        internal virtual void SetValue(String value) { throw new NotImplementedException(); }
        internal virtual void SetValue(decimal value) { throw new NotImplementedException(); }
        internal void SetValue(int value) { SetValue((decimal)value); }

        internal byte[] BytesValue { get { return _bytes; } }
        public byte[] RoBytesValue { get { return (byte[])BytesValue.Clone(); } }
        public String StringValue { get { return _string;  } }
        public decimal? DecimalValue { get { return _decimal; } }
        public int? IntValue { get { return (int?)_decimal; } }
    }

    public class NMessageField : MessageField
    {
        public override int GetBytesLengthFromActualLength(int actualLength)
        {
            if (actualLength <= 0) return 0;
            return (actualLength - 1) / 2 + 1;
        }

        internal override void SetValue(byte[] value)
        {
            if (VarLength && Length == null)
            {
                Length = value.Length * 2;
                if (IsOdd && Length > 0) Length = Length - 1;
            }
            if (Length == null)
                throw new MessageProcessorException("The Length property still null (not yet set).");
            if (value.Length > (Length+1)/2)
                throw new MessageProcessorException("The length of the assigned value is greater than defined length (Length property).");

            String str = MessageUtility.HexToString(value);
            foreach (char ch in str)
            {
                if (ch < '0' || ch > '9')
                    throw new MessageProcessorException("Invalid assigned value, cannot converted to numeric.");
            }
            if (str.Length < Length)
                str = str.PadLeft((int)Length, '0');
            else if (str.Length > Length) // str.Length == Length + 1 //odd effect
            {
                if (VarLength) str = str.Substring(0, str.Length-1);
                else str = str.Substring(1);
            }

            if (value.Length * 2 < Length)
            {
                byte[] val = new byte[((int)Length + 1) / 2 - value.Length]; //padded zero bytes
                value = val.Concat(value).ToArray();
            }
            _bytes = value;
            
            _string = str;
            if (FracDigits > 0) _string = _string.Insert(_string.Length - FracDigits, ".");
            
            _decimal = MessageUtility.HexToDecimal(value, FracDigits);
        }

        internal override void SetValue(String value)
        {
            SetValue(MessageUtility.StringToHex(value));
        }

        internal override void SetValue(decimal value)
        {
            SetValue(MessageUtility.DecimalToHex(value, FracDigits));
            if (_decimal == null)
            {
                _decimal = value;
                if (FracDigits > 0) _string = _string.Insert(_string.Length - FracDigits, ".");
            }
        }
    }

    public class NsMessageField : NMessageField
    {
        internal override void SetValue(byte[] value)
        {
            if (VarLength && Length == null)
            {
                Length = value.Length * 2;
                if (IsOdd && Length > 0) Length = Length - 1;
            }
            if (Length == null)
                throw new MessageProcessorException("The Length property still null (not yet set).");
            if (value.Length > (Length + 1) / 2)
                throw new MessageProcessorException("The length of the assigned value is greater than defined length (Length property).");

            String str = MessageUtility.HexToString(value);
            if (str.Length < Length)
                str = str.PadLeft((int)Length, '0');
            else if (str.Length > Length) // str.Length == Length + 1 //odd effect
            {
                if (VarLength) str = str.Substring(0, str.Length - 1); //left aligned
                else str = str.Substring(1); //right aligned
            }

            if (value.Length * 2 < Length)
            {
                byte[] val = new byte[((int)Length + 1) / 2 - value.Length]; //padded zero bytes
                value = val.Concat(value).ToArray();
            }
            
            _bytes = value;
            _string = str;
            _decimal = null;
        }
    }

    public class AnMessageField : MessageField
    {
        public override int GetBytesLengthFromActualLength(int actualLength)
        {
            if (actualLength <= 0) return 0;
            return actualLength;
        }

        internal override void SetValue(byte[] value)
        {
            for (int i = 0; i < value.Length; i++)
            {
                if (value[i] < 0x30 || value[i] > 0x39)
                    throw new MessageProcessorException("Invalid assigned value, cannot converted to numeric");
            }
            if (VarLength && Length == null) Length = value.Length;
            if (Length == null)
                throw new MessageProcessorException("The Length property still null (not yet set).");
            else if (value.Length > Length)
                throw new MessageProcessorException("The length of the assigned value is greater than defined length (Length property).");
            else if (value.Length < Length)
            {
                byte[] val = new byte[(int)Length - value.Length];
                for (int i = 0; i < val.Length; i++) val[i] = (byte)0x30; //'0'
                value = val.Concat(value).ToArray();
            }
            _bytes = value;
            _string = MessageUtility.AsciiArrayToString(value);
            if (FracDigits > 0) _string = _string.Insert(_string.Length - FracDigits, ".");
            _decimal = decimal.Parse(_string);
        }

        internal override void SetValue(String value)
        {
            decimal? val = MessageUtility.StringToDecimal(value);
            if (val == null) throw new MessageProcessorException("Invalid string, cannot converted to numeric."); 
            SetValue(MessageUtility.DecimalToAsciiArray((decimal)val, FracDigits));
        }

        internal override void SetValue(decimal value)
        {
            SetValue(MessageUtility.DecimalToAsciiArray(value, FracDigits));
        }
    }

    public class AnsMessageField : MessageField
    {
        public override int GetBytesLengthFromActualLength(int actualLength)
        {
            if (actualLength <= 0) return 0;
            return actualLength;
        }

        internal override void SetValue(byte[] value)
        {
            if (VarLength && Length == null) Length = value.Length;
            if (Length == null)
                throw new MessageProcessorException("The Length property still null (not yet set).");
            else if (value.Length > Length)
                throw new MessageProcessorException("The length of the assigned value is greater than defined length (Length property).");
            else if (value.Length < Length)
            {
                byte[] val = new byte[(int)Length - value.Length];
                for (int i = 0; i < val.Length; i++) val[i] = (byte)0x20; //Space
                value = value.Concat(val).ToArray();
            }
            _bytes = value;
            _string = MessageUtility.AsciiArrayToString(value);
            _decimal = null;
        }

        internal override void SetValue(String value)
        {
            SetValue(MessageUtility.StringToAsciiArray(value));
        }

        internal override void SetValue(decimal value)
        {
            SetValue(MessageUtility.DecimalToAsciiArray(value, FracDigits));
            _decimal = value;
            if (FracDigits > 0) _string.Insert(_string.Length - FracDigits, ".");
        }
    }

    public class ParsedMessageContainer
    {
        private List<IMessageHeader> _headers = new List<IMessageHeader>();
        private Dictionary<int,MessageField> _fields = new Dictionary<int,MessageField>();
        
        internal IList<IMessageHeader> Headers { get { return _headers; } }
        internal IDictionary<int, MessageField> Fields { get { return _fields; } }
        public IList<IMessageHeader> RoHeaders { get { return _headers.AsReadOnly(); } }
        public IList<KeyValuePair<int, MessageField>> RoFields
        { 
            get
            {
                return new ReadOnlyCollection<KeyValuePair<int, MessageField>>(_fields.ToList()); 
            }
        }
    }


    public class CompiledMessageField
    {
        internal byte[] Header { get; set; }
        public byte[] RoHeader { get { return (byte[])Header.Clone();  } }
        public MessageField Content { get; internal set;  }
        public int Length { get { return (Header == null ? 0 : Header.Length)
            + (Content == null || Content.BytesValue == null ? 0 : Content.BytesValue.Length); }
        }
        public int HeaderLength { get { return Header == null ? 0 : Header.Length; } }
    }
    
    public class CompiledMessageContainer
    {
        private List<IMessageHeader> _headers = new List<IMessageHeader>();
        private Dictionary<int, CompiledMessageField> _fields = new Dictionary<int, CompiledMessageField>();
        private int _bitMapIdx = -1;

        public int LengthHeader { get; internal set; }

        internal IList<IMessageHeader> Headers { get { return _headers; } }
        internal IDictionary<int, CompiledMessageField> Fields { get { return _fields; } }
        
        public IList<IMessageHeader> RoHeaders { get { return _headers.AsReadOnly(); } }
        public IList<KeyValuePair<int, CompiledMessageField>> RoFields
        {
            get
            {
                return new ReadOnlyCollection<KeyValuePair<int, CompiledMessageField>>(_fields.ToList());
            }
        }

        internal void AddHeader(IMessageHeader hdr)
        {
            _headers.Add(hdr);
            if (hdr is MessageBitMap) _bitMapIdx = _headers.Count-1;
        }

        internal void SetBitOn(IList<int> bitPos)
        {
            if (_bitMapIdx >= 0) ((MessageBitMap)_headers[_bitMapIdx]).SetBitOn(bitPos);
        }

        public byte[] GetAllBytes()
        {
            int length = 0;
            foreach (IMessageHeader hdr in _headers) length += hdr.Length;
            foreach (CompiledMessageField fld in _fields.Values) length += fld.Length;
            
            byte[] bytes = new byte[LengthHeader + length];
            byte[] lenHdr = MessageUtility.IntToBytes((ulong)length, LengthHeader);
            Array.Copy(lenHdr, 0, bytes, 0, LengthHeader);

            IList<int> bitOnList = _fields.Keys.ToList();
            SetBitOn(bitOnList);
            
            int i = LengthHeader;
            foreach (IMessageHeader hdr in _headers)
            {
                Array.Copy(hdr.Bytes, 0, bytes, i, hdr.Length);
                i += hdr.Length;
            }
            
            int[] bits = bitOnList.ToArray();
            Array.Sort(bits);
            for (int j=0; j<bits.Length; j++)
            {
                CompiledMessageField fld = _fields[bits[j]];
                if (fld.Header != null) Array.Copy(fld.Header, 0, bytes, i, fld.HeaderLength);
                Array.Copy(fld.Content.BytesValue, 0, bytes, i + fld.HeaderLength, fld.Length - fld.HeaderLength);
                i += fld.Length;
            }
            return bytes;
        }
    }
}
